#include <windows.h>
#include "CBaseEntity.h"
#include "CNpc.h"
#include "CChar.h"
#include "CGlobal.h"
#include "CUtil.h"
#include "CActionPacket.h"
#include "CStatusEffect.h"
#include "define.h"
#include "CConsole.h"
#include <math.h>

CBaseEntity::CBaseEntity(void) {
	this->m_isCasting = false;
	memset(this, 0, sizeof(CBaseEntity));
	this->incombat = false;
	this->m_modDef = 0;
	this->m_modAcc = 0;
	this->m_modAtt = 0;
}

CBaseEntity::~CBaseEntity(void) {
}

bool CBaseEntity::isCasting() {
	return this->m_isCasting;
}


unsigned int CBaseEntity::getId() {
	return this->m_id;
}

void CBaseEntity::setId(unsigned int id) {
	this->m_id = id;
}

unsigned short CBaseEntity::getTargId() {
	return this->m_targId;
}

void CBaseEntity::setTargId(unsigned int targid) {
	this->m_targId = targid;
}

bool CBaseEntity::isDead() {
	switch(this->getObjectType()) {
		case OBJ_NPC:
		case OBJ_MOB:
			return (((CNpc*)this)->h.hp <= 0);
		case OBJ_PC:
			return (this->h.hp <= 0);
	}
	return true;
}

char * CBaseEntity::getName() {
//	CConsole::outDeb("Char: %s (Length: %d)",this->m_name,strlen(this->m_name));
	return (this->m_name);
}

char * CBaseEntity::getStrippedName() {

	memset(this->m_nameStripped,0,17);

	int j = 0;
	for(unsigned int i = 0; i < strlen(this->m_name); i++) {
		
		if(	this->m_name[i] != ' ' &&
			this->m_name[i] != '.' &&
			this->m_name[i] != ',' &&
			this->m_name[i] != ' ' &&
			this->m_name[i] != '-' &&
			this->m_name[i] != '\''&&
			this->m_name[i] != '~') {
			
				this->m_nameStripped[j++] = this->m_name[i];
		}
		if (j>=16) {
			CConsole::outDeb("Stripping the name '%s' would result in a name longer than 16 characters!",m_name);
			break;
		}
	}

	return this->m_nameStripped;

}

float CBaseEntity::getXPos() {
	return this->loc.p.x;
}

float CBaseEntity::getYPos() {
	return this->loc.p.y;
}

float CBaseEntity::getZPos() {
	return this->loc.p.z;
}

unsigned short CBaseEntity::getRot() {
	return this->loc.p.rotation;	
}

void CBaseEntity::setPos(struct position p) {
	this->loc.p = p;
}

void CBaseEntity::setXPos(float x) {
	this->loc.p.x = x;
}

void CBaseEntity::setYPos(float y) {
	this->loc.p.y = y;
}

void CBaseEntity::setZPos(float z) {
	this->loc.p.z = z;
}

void CBaseEntity::setObjType(unsigned short objType) {
	this->m_objType = objType;
}

bool CBaseEntity::isPlayer() {
	return (this->m_objType == OBJ_PC);
}

bool CBaseEntity::isNPC() {
	return ((this->m_objType == OBJ_NPC) || (this->m_objType == OBJ_MOB));
}

int CBaseEntity::getMaxHp() {
	switch(this->m_objType) {
		case OBJ_MOB:
		case OBJ_NPC:
			return ((CNpc*)this)->m_maxHp;
		case OBJ_PC:
			return ((CChar*)this)->getMaxHp();	
	}
	return 0;
}

int CBaseEntity::getMaxMp() {
	switch(this->m_objType) {
		case OBJ_MOB:
		case OBJ_NPC:
			return ((CNpc*)this)->m_maxMp;
		case OBJ_PC:
			return ((CChar*)this)->getMaxMp();	
	}
	return 0;
}


unsigned short CBaseEntity::getWeaponDamage() {

	switch(this->m_objType) {
		case OBJ_MOB:
		case OBJ_NPC:
			return ((CNpc*)this)->getWeaponDamage();
		case OBJ_PC:
			return ((CChar*)this)->getWeaponDamage(SLOT_MAIN);	
	}
	return 0;

}


bool CBaseEntity::isMob() {
	return (this->m_objType == OBJ_MOB);
}

unsigned short CBaseEntity::getBaseDef() {
	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->getBaseDef();
			break;
		case OBJ_PC:
			return ((CChar*)this)->getBaseDef();
			break;
	}
	return 0;
}

unsigned short CBaseEntity::getDef() {
	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->getBaseDef();
			break;
		case OBJ_PC:
			return ((CChar*)this)->getBaseDef() + ((CChar*)this)->m_modDef;
			break;
	}
	return 0;
}

unsigned short CBaseEntity::getBaseAttack() {
	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->getBaseAttack();
			break;
		case OBJ_PC:
			return ((CChar*)this)->getBaseAttack();
			break;
	}
	return 0;
}

unsigned short CBaseEntity::getAttack() {
	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->getBaseAttack();
			break;
		case OBJ_PC:
			return ((CChar*)this)->getBaseAttack() + ((CChar*)this)->m_modAtt;
			break;
	}
	return 0;
}

int CBaseEntity::getObjectType() {
	return this->m_objType;
}

unsigned short CBaseEntity::getZone() {
	return this->loc.zone;
}

void CBaseEntity::setZone(unsigned short zone) {
	this->loc.zone = (unsigned char)zone;
}

unsigned char CBaseEntity::getAnimation() {
	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->h.animation;
			break;
		case OBJ_PC:
			return ((CChar*)this)->h.animation;
			break;
	}
	return 0;
}

void CBaseEntity::setAnimation(unsigned char anim) {
	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			((CNpc*)this)->h.animation = anim;
			break;
		case OBJ_PC:
			((CChar*)this)->h.animation = anim;
			break;
	}
}

void CBaseEntity::setAltAnimation(unsigned char anim) {
	this->m_animationAlt = anim;
}

position CBaseEntity::getPos() {
	return this->loc.p;
}

int CBaseEntity::getHp() {
	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->h.hp;
			break;
		case OBJ_PC:
			return ((CChar*)this)->h.hp;
			break;
	}
	return 0;
}

short CBaseEntity::getMp() {
	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->h.mp;
			break;
		case OBJ_PC:
			return ((CChar*)this)->h.mp;
			break;
	}
	return 0;
}

short CBaseEntity::getHpp() {
		switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->h.hpp;
			break;
		case OBJ_PC:
			return ((CChar*)this)->h.hpp;
			break;
	}
	return 0;
}

short CBaseEntity::getMpp() {
		switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			return ((CNpc*)this)->h.mpp;
			break;
		case OBJ_PC:
			return ((CChar*)this)->h.mpp;
			break;
	}
	return 0;
}


float CBaseEntity::getMaxCRatio(CBaseEntity *target) {
	float ratio = ((float)(this->getAttack()) / (float)(target->getDef()));
	float cRatio = 0;
	float cRatioMax = 0;


	if(this->getObjectType() == OBJ_PC) {
		if(this->m_lvlMain < target->m_lvlMain) {
			cRatio = ratio - (0.050f * (target->m_lvlMain - this->m_lvlMain));
		} else {
			cRatio = ratio;
		}
	} else {
		cRatio = ratio;
	}

	if( cRatio < 0 ) {
		cRatio = 0;
	}
	if( cRatio > 2 ) {
		cRatio = 2;
	} 

	if((0 <= cRatio) && (cRatio < 0.5)) {
		cRatioMax = 0.4f + 1.2f * cRatio;
	} else if((0.5 <= cRatio) && (cRatio <= (5/6))) {
		cRatioMax = 1;
	} else if(((5/6) < cRatio) && (cRatio <= 2)) {
		cRatioMax = 1.2f * (cRatio);
	}


	return cRatioMax;
}

float CBaseEntity::getMinCRatio(CBaseEntity *target) {
	float ratio = ((float)(this->getAttack()) / (float)(target->getDef()));
	float cRatio = 0;
	float cRatioMin = 0;

	if(this->getObjectType() == OBJ_PC) {
		if(this->m_lvlMain < target->m_lvlMain) {
			cRatio = ratio - (0.050f * (target->m_lvlMain - this->m_lvlMain));
		} else {
			cRatio = ratio;
		}
	} else {
		cRatio = ratio;
	}

	if( cRatio < 0 ) {
		cRatio = 0;
	} 
	if( cRatio > 2 ) {
		cRatio = 2;
	} 
	

	if((0 <= cRatio) && (cRatio < 1.25)) {
		cRatioMin =  (float)(-0.5 + 1.2 * cRatio);
	} else if((1.25 <= cRatio) && (cRatio <= 1.5)) {
		cRatioMin = 1;
	} else if((1.5 < cRatio) && (cRatio <= 2)) {
		cRatioMin = (float)(-0.8 + 1.2 * cRatio);
	}

	if (cRatioMin < 0) {
		cRatioMin = 0;
	}

	return cRatioMin;
}

void CBaseEntity::die() {
	switch(this->m_objType) {
		case OBJ_MOB:
		case OBJ_NPC:
			((CNpc*)this)->die();
			break;
		case OBJ_PC:
			((CChar*)this)->die();
			break;
	}
}

void CBaseEntity::takePhysicalDamage(int damage, CBaseEntity * pDamageDealer) {

	switch(this->m_objType) {
		case OBJ_NPC:
		case OBJ_MOB:
			((CNpc*)this)->takePhysicalDamage(damage, pDamageDealer);
			break;
		case OBJ_PC:
			((CChar*)this)->takePhysicalDamage(damage, pDamageDealer);
			break;
	}

}

void CBaseEntity::weaponskill(unsigned short weaponskill, unsigned short damage, CBaseEntity * target) {

	if(target == NULL) {
		return;
	}

	if(this->h.tp >= 100) {
		CActionPacket* attackPacket=new CActionPacket();

		attackPacket->setActorId(this->m_id);
		attackPacket->setCategorie(3);
		attackPacket->setParameter1(weaponskill);

		apActionEntry newEntry;
		memset(&newEntry,0,sizeof(newEntry));

		newEntry.unknownValue=0x18;

		int base = 0;
		int mod = (weaponskill%0x10);

		switch(weaponskill/0x10) {
			case 0x00:	base = (0x0780 / 0x80); break;
			case 0x01:	base = (0x0F80 / 0x80); break;
			case 0x02:	base = (0x0080 / 0x80); break;
			case 0x03:	base = (0x3500 / 0x80); break;
			case 0x04:	base = (0x1700 / 0x80); break;
			case 0x05:	base = (0x2D80 / 0x80); break;
			case 0x06:	base = (0x1E80 / 0x80); break;
			case 0x07:	base = (0x3C80 / 0x80); break;
			case 0x08:	base = (0x4B80 / 0x80); break;
			case 0x09:	base = (0x5300 / 0x80); break;
			case 0x0A:	base = (0x2600 / 0x80); break;
			case 0x0B:	base = (0x4400 / 0x80); break;
			case 0x0C:	base = (0xE000 / 0x80); break;
			case 0x0D:	base = (0x6200 / 0x80); break;
		}

		newEntry.targetAnimationID = base+mod;
		newEntry.unknownValue2=0x020;

		newEntry.messageType=0x00B9;
	
		newEntry.parameter2=damage;

		attackPacket->setTargetID(1,target->m_id);
		attackPacket->setActionEntry(1,1,&newEntry);


		attackPacket->finalizePacket();
		attackPacket->savePacket();

		CChar::insertPacketGlob(CHAR_INRANGE,*attackPacket,this);

		this->h.tp = 12;
		if(this->isPlayer()) { 
			((CChar*)this)->updateHealth();
		}
	}
}

void CBaseEntity::addStatusEffect(CStatusEffect * statusEffect) {
	unsigned char type = statusEffect->getType();
	CStatusEffect * tempStatus = getStatusEffect(type);
	if(tempStatus != NULL) {
		// if new negative status has a more negative value, overwrite the status
		if( ( (tempStatus->getValue() < 0) && (statusEffect->getValue() < tempStatus->getValue()) ) ||
		// if new status has a higher value, overwrite the status
			( (tempStatus->getValue() > 0) && (statusEffect->getValue() > tempStatus->getValue()) ) ) {
				removeStatusEffect(tempStatus);
		}
	}
	m_statusEffectList.push_back(statusEffect);
}

CStatusEffect * CBaseEntity::getStatusEffect(unsigned char type) {
	unsigned char size = (unsigned char)m_statusEffectList.size();
	for(unsigned long count = 0; count < size; count ++) {
		CStatusEffect * statusEffect = m_statusEffectList.at(count);
		if(statusEffect != NULL) {
			if(statusEffect->getType() == type) {
				return statusEffect;
			}
		}
	}
	return NULL;
}

void CBaseEntity::removeStatusEffect(CStatusEffect * statusEffect) {
	unsigned char size = (unsigned char)m_statusEffectList.size();
	for(unsigned long count = 0; count < size; count ++) {
		CStatusEffect * tempEffect = m_statusEffectList.at(count);
		if( (tempEffect != NULL) && (tempEffect == statusEffect) ) {
			m_statusEffectList.erase(m_statusEffectList.begin() + count);
			return;
		}
	}
}


unsigned short CBaseEntity::getHitRate(CBaseEntity * target) {

	//Getting variables
	int hitrate = 75;
	//Note: Level correction has already been applied.
	int playeracc = this->getAccuracy(target); 
	int targeteva = target->getEva();
	int hitratebonus = 0;
	int hitratepenalty = 0;
	int hitratemod = 0;

	//comparing acc and eva
	if (playeracc > targeteva) {
		hitratebonus = (playeracc - targeteva)/2;
	}
	if (targeteva > playeracc) {
		hitratepenalty = (targeteva - playeracc)/2;
	}

	//Applying modifications
	hitratemod = hitratebonus - hitratepenalty;
	hitrate += hitratemod;

	//Applying caps
	if (hitrate > 95) {
		hitrate = 95;
	}
	if (hitrate < 20) {
		hitrate = 20;
	}

	return hitrate;

}

unsigned short CBaseEntity::getBaseEva() {

	switch(this->m_objType) {
		case OBJ_MOB:
		case OBJ_NPC:
			return ((CNpc*)this)->getBaseEva();
			break;
		case OBJ_PC:
			return ((CChar*)this)->getBaseEva();
			break;
	}

	return 0;
}

unsigned short CBaseEntity::getEva() {

	switch(this->m_objType) {
		case OBJ_MOB:
		case OBJ_NPC:
			return ((CNpc*)this)->getBaseEva();
			break;
		case OBJ_PC:
			return ((CChar*)this)->getBaseEva();
			break;
	}

	return 0;
}

unsigned short CBaseEntity::getBaseAccuracy(CBaseEntity * target) {

	switch(this->m_objType) {
		case OBJ_MOB:
		case OBJ_NPC:
			return ((CNpc*)this)->getBaseAccuracy(target);
			break;
		case OBJ_PC:
			return ((CChar*)this)->getBaseAccuracy(target);
			break;
	}
	return 0;

}

unsigned short CBaseEntity::getAccuracy(CBaseEntity * target, unsigned int weaponslot) {

	switch(this->m_objType) {
		case OBJ_MOB:
		case OBJ_NPC:
			return ((CNpc*)this)->getBaseAccuracy(target);
			break;
		case OBJ_PC:
			return ((CChar*)this)->getBaseAccuracy(target) + ((CChar*)this)->m_modAcc;
			break;
	}
	return 0;

}

void CBaseEntity::missedAttackAction( CBaseEntity* target) {


	CActionPacket* attackPacket=new CActionPacket();

	attackPacket->setActorId(this->getId());
	attackPacket->setCategorie(1);
	attackPacket->setStartAnimationCategorie(0x1D);
	attackPacket->setStartAnimation(0x306B);
	attackPacket->setParameter1(0x0061);

	apActionEntry newEntry;
	memset(&newEntry,0,sizeof(newEntry));

	newEntry.unknownValue=0x09;

	newEntry.messageType=0x000f;



	attackPacket->setTargetID(1,target->m_id);
	attackPacket->setActionEntry(1,1,&newEntry);


	attackPacket->finalizePacket();
	attackPacket->savePacket();

	if(target->getObjectType() == OBJ_MOB) {
		target->takePhysicalDamage(0, this);
	}

	if(target->getObjectType() == OBJ_PC) {
		((CChar*)target)->insertPacket(CHAR_INRANGE_SELF,*attackPacket);
	} else if(this->getObjectType() == OBJ_PC) {
		((CChar*)this)->insertPacket(CHAR_INRANGE_SELF,*attackPacket);
	}

	
}

bool CBaseEntity::isFaceing(struct position p) {
	
	//Entity's true heading
	unsigned char truerot = (unsigned char)this->getRot();

	//Desired heading
	if ((p.x - this->loc.p.x) == 0) //TODO: Doesn't this alter our result?
		return false; //No division by 0.

	float angle = atan2((p.z - this->loc.p.z),(p.x - this->loc.p.x)); //Arctangent of the slope of the line between entity and target.
	unsigned char rot = (unsigned char) ((2*3.14159265358979 - angle) * 256 / (2*3.14159265358979)); //Adjust from radians to SE's 256 degree system.
	
	unsigned char diff = (unsigned char) (abs(truerot - rot)); //The absolute value of the deviation from desired heading.
	if (diff <= 40 || diff >= 216) //Allowed deviation.  The extra case is if the target is due east (compare 254 to 2 for example).
		return true; //Within acceptable range.
	return false; //Not within acceptable range.

}

//Gets the Volitile Enmity Modifer
float CBaseEntity::getVModifier() {
	// 240 / { floor( 31 * [ Level ] / 50 ) + 6 }
	return (240.0f / (float)((int)((31 * m_lvlMain) / 50 ) + 6));
}

//Gets the Cumulative Enmity Modifier
float CBaseEntity::getCModifier() {
	// 80 / { floor( 31 * [ Level ] / 50 ) + 6 }
	return (80.0f / (float)((int)((31 * this->m_lvlMain) / 50 ) + 6));
}

void CBaseEntity::updateEnmity(CBaseEntity * entity, int vEnmity,int cEnmity) {
	// If we are casting on a player then we have to add the enmity to every mob thats aggressive to that player
	if ( this->isPlayer() ) {
		//Player that the spell has been casted on
		CChar * tmpPlayer = (CChar *)this;
		//tmpEntity is every mob that the casted person has on his enmity list
		CBaseEntity * tmpEntity = NULL;
		//Lock the enmity list so we don't access the resource at the same time another thread does
		tmpPlayer->hasEnmityList->lock();
		// loop for every enemy tmpPlayer has on his enmityList
		for (unsigned int x = 0; x < tmpPlayer->hasEnmityList->getSize(); x++) {
			//Get the mob that is on the tmpPlayers Enmity List
			tmpEntity = tmpPlayer->hasEnmityList->at(x);
			if (tmpEntity != NULL) {
				//If its not null 
				//Verify that it is a mob, and we only care about mobs in range
				if ( (tmpEntity->isMob()) && (CUtil::distance(tmpEntity->getPos(),this->getPos()) < 25) ) {
					//This player is now an enemy of the mob
					if (((CChar *)entity)->hasEnmityList->find(tmpEntity->getId()) == NULL) {
						((CChar *)entity)->hasEnmityList->pushBack(tmpEntity->getId(),tmpEntity);
					}
					//give some enmity to the person casting the spell because the mob saw it
					((CNpc *)tmpEntity)->updateVCEnmity(entity,vEnmity,cEnmity);

				}
			}
		}
		tmpPlayer->hasEnmityList->unlock();
	} else if ( (this->isMob()) && (entity != NULL) ) { //We are doing an action on a mob
		if (entity->isPlayer()) {
			((CChar *)entity)->hasEnmityList->lock();
			if (((CChar *)entity)->hasEnmityList->find(this->getId()) == NULL) {
				((CChar *)entity)->hasEnmityList->pushBack(this->getId(),this);
			}
			((CChar *)entity)->hasEnmityList->unlock();
		}
		((CNpc *)this)->updateVCEnmity(entity, vEnmity, cEnmity);
	}
}

bool CBaseEntity::isRangedAttacking() {
	return m_isRangedAttacking;
}

int CBaseEntity::adjustDamage(CWeaponItem * weapon, int damage) {
	if(weapon == NULL) 
	{
		return damage;
	}

	float calculatedDamage = (float)damage;
	if (weapon->getSkillType() < 25) {
	short type = weapon->getDmgType();
		switch(type) {
			case 1: {
				calculatedDamage *= this->r.pierce;
				break;
			}
			case 2: {
				calculatedDamage *= this->r.slash;
				break;
			}
			case 3: {
				calculatedDamage *= this->r.impact;
				break;
			}
			case 4: {
				calculatedDamage *= this->r.handtohand;
				break;
			}
		}
	} else {
		calculatedDamage *= this->r.range;
	}
	return (int)calculatedDamage;
}